﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Barebones.Framework;
using Barebones.Components;
using Barebones.Dependencies;
using System.Diagnostics;

namespace Barebones.Structure
{
    class LogicContainer : IUpdateable, IDrawable
    {
        Dictionary<Type, IDependent> m_Items = new Dictionary<Type, IDependent>();
        List<IUpdateable> m_Updateable = new List<IUpdateable>();
        List<IDrawable> m_Drawable = new List<IDrawable>();

        public void Add(IDependent item)
        {
            Debug.Assert(!m_Items.ContainsKey(item.GetType()));

            Link(item);

            m_Items.Add(item.GetType(), item);

            if (item is IUpdateable)
                m_Updateable.Insert(0, item as IUpdateable);

            if (item is IDrawable)
                m_Drawable.Insert(0, item as IDrawable);
        }

        public void Remove(IDependent item)
        {
            m_Items.Remove(item.GetType());

            UnLink(item);

            if (item is IUpdateable)
                m_Updateable.Remove(item as IUpdateable);

            if (item is IDrawable)
                m_Drawable.Remove(item as IDrawable);
        }

        public T GetFirstImplementing<T>() where T : class
        {
            foreach(IDependent item in m_Items.Values)
            {
                if (typeof(T).IsAssignableFrom(item.GetType()))
                    return item as T;
            }

            return null;
        }

        public IEnumerable<T> GetAllImplementing<T>() where T : class
        {
            foreach (IDependent item in m_Items.Values)
            {
                if (typeof(T).IsAssignableFrom(item.GetType()))
                    yield return item as T;
            }
        }

        public T Get<T>() where T : class, IDependent
        {
            IDependent item = default(T);
            m_Items.TryGetValue(typeof(T), out item);
            return item as T;
        }

        public void Link(IDependent item)
        {
            Action<IDependency, Object> linkFunc = (depencency, other) => depencency.Link(other);

            BindItemToOthers(item, linkFunc);
            BindOthersToItem(item, linkFunc);
        }

        public void UnLink(IDependent item)
        {
            Action<IDependency, Object> unlink = (depencency, other) => depencency.UnLink(other);

            BindItemToOthers(item, unlink);
            BindOthersToItem(item, unlink);
        }

        private void BindOthersToItem(IDependent item, Action<IDependency, Object> unlink)
        {
            foreach (IDependent dependent in m_Items.Values)
            {
                foreach (IDependency dependency in dependent.GetDependencies())
                {
                    if (dependency.IsFor(item))
                        unlink(dependency, item);
                }
            }
        }

        private void BindItemToOthers(IDependent item, Action<IDependency, Object> link)
        {
            foreach (IDependency dependency in item.GetDependencies())
            {
                foreach (IDependent dependent in m_Items.Values)
                {
                    if (dependency.IsFor(dependent))
                        link(dependency, dependent);
                }
            }
        }

        public void Update(float dt)
        {
            m_Updateable.ForReversed(item => item.Update(dt));
        }

        public void Draw()
        {
            m_Drawable.ForReversed(item => item.Draw());
        }
    }
}
